
// C2Sub.swift
// Created by zhu xietong on 2023/11/23.

import Foundation

public protocol DisposableBase {
    func dispose()
}

open class DisposableObj {
    var ID: String = ""
    var didSetBag: (_: DisposeBag?, _: DisposableObj) -> Void = { _, _ in }
    weak var bag: DisposeBag? = nil {
        didSet {
            didSetBag(bag, self)
        }
    }

    public func disposed(by: DisposeBag) -> Self {
        self.bag = by
        return self
    }

    open func dispose() {
        // Implementation of dispose, if needed
    }
}

public class DisposeBag {
    var observers: [DisposableObj] = []

    public init() {
        // Initialization, if needed
    }
}

class BagWrap {
    weak var bag: DisposeBag? = nil
}

public enum Ignore {
    case none
    case equal
    case `func`((_ new: Any, _ old: Any) -> Bool)
}

public class PublishSub<T> {
    public init() {
        // Initialization, if needed
    }

    var value: T? = nil

    public typealias Block = (_: T) -> Void

    public class Observer: DisposableObj {
        var ignore: Ignore = .none
        var block: Block? = nil

        init(block: @escaping Block) {
            self.block = block
        }

        var recentValue: T? = nil

        func receive(value: T) {
            switch ignore {
            case .none:
                break
            case .equal:
                if let recent = recentValue, "\(recent)" == "\(value)" {
                    recentValue = value
                    return
                }
            case .func(let action):
                if let recent = recentValue, action(value, recent) {
                    recentValue = value
                    return
                }
            }

            block?(value)
            recentValue = value
        }

        public override func dispose() {
            self.block = nil
        }

        @discardableResult
        public override func disposed(by: DisposeBag) -> Self {
            self.bag = by
            return self
        }
    }

    var bags: [BagWrap] = []

    var publishID: String = UUID().uuidString

    public func subscribe(_ block: @escaping Block) -> Observer {
        let ob = Observer(block: block)
        ob.ignore = tempIgnore ?? .equal
        ob.ID = publishID
        let bag = BagWrap()

        ob.didSetBag = { bg, obj in
            bag.bag = bg
            if let bg_ = bg {
                bg_.observers.append(obj)
            }
        }
        bags.append(bag)
        self.tempIgnore = nil
        return ob
    }
    

    var tempIgnore: Ignore? = nil

    public func ignore(_ ignore: Ignore) -> Self {
        tempIgnore = ignore
        return self
    }

    @discardableResult
    public func onNext(_ value: T) -> Self {
        for item in bags {
            if let bag = item.bag {
                for observer in bag.observers {
                    if observer.ID == publishID, let obj = observer as? Observer {
                        obj.receive(value: value)
                    }
                }
            }
        }
        return self
    }
}

@propertyWrapper public class Publish<Value> {
    public var value: Value
    public var projectedValue: PublishSub<Value> = PublishSub<Value>()

    public var wrappedValue: Value {
        get { value }
        set {
            value = newValue
            projectedValue.onNext(newValue)
        }
    }

    public init(wrappedValue defaultValue: Value) {
        self.value = defaultValue
    }
}
